﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;


using imaginecup.information;
//using componentSystem.information.malaria;
using imaginecup.util;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Imaginecup.iterator;

namespace imaginecup.objectManager
{
   

    public class ObjectManager
    {

        //구현 컴포넌트별로 Object Key와 IComponent(실제구현 컴포넌트 인스턴스)쌍으로 저장할 자료구조 
        private Dictionary<GObjectID, IComponent>[] m_ComponentTypeToComponentMap;
          //  = new Dictionary<GObjectID, IComponent>[EnumIDInfomation.NUM_COMPONENT_TYPE_IDS];
        
        //게임별로 필요한 초기화 정보를 처리함.
        private IComponentInit m_ComponentInit;

        //오브젝트 매니저에서 사용하는 인터페이스와 메시지에 대한 등록정보를 관리함.
        //오브젝트 매니저 데이터가 초기화 부분에서만 사용되면 추후 가비지 컬렉터에의해 ObjManData가 사용되지 않는다고 판단하여 해제될 수 있다.?????
        private ObjectManagerData m_ObjectManData;

        //메시지가 핸들링하는 중에는 삭제를 못한다. 삭제 등록만 할 수있다.
        private int m_MessageHandleCount = 0;

        //삭제될 오브젝트들을 등록해 놓는다.
        private List<GObjectID> m_DestoryGObjectList = new List<GObjectID>();
        //추가될 오브젝트들을 등록한다.
        private List<IComponent> m_AddComponentList = new List<IComponent>();

        public ObjectManager()
        {        
        }
        


        //객체가 인터페이스를 지원하면 인터페이스를 지원하는 Component 인스턴스를 리턴한다.
        //지원하지 않으면 NULL 리턴(단순하게 생각하기 위해 오브젝트별로 인터페이스(패밀리)를
        //구현한 하나의 컴포넌트만 가지고 있다.)
        public IComponent QueryInterface(GObjectID a_GID , SInterfaceID a_SInterfaceID)
        {
            //인터페이스(패밀리)를 지원하는 컴포넌트 아이디 리스트
            List<SComponentTypeID> compTypeList = m_ObjectManData.GetInterfaceTypeInfoList()[a_SInterfaceID.InterfaceID];

            foreach (SComponentTypeID compTypeID in compTypeList)
            {
                //컴포넌트별 인스턴스들의 집합. 키 값 = 오브젝트 ID
                Dictionary<GObjectID, IComponent> compMap = m_ComponentTypeToComponentMap[compTypeID.ComponentTypeID];

                //검색한다.
                foreach (KeyValuePair<GObjectID, IComponent> pair in compMap)
                {
                    //지원하는 오브젝트 중에 매개변수로 들어온 아이디와 같은 아이디가 있으면
                    //해당 컴포넌트 인스턴스 리턴.
                    
                    if (pair.Key.Equals(a_GID))
                    {
                        return pair.Value;
                    }
                    
                }

            }
            return null;
        }

       

        //컴포넌트 인스턴스가 특정 인터페이스(패밀리)를 지원하는지 확인
        public bool IsInterfaceImplementedByComponent(SInterfaceID a_SInterfaceID , IComponent a_Component)
        {
            foreach (SComponentTypeID compType in m_ObjectManData.GetInterfaceTypeInfoList()[a_SInterfaceID.InterfaceID])
            {
                if (compType.ComponentTypeID == a_Component.GetComponentTypeID().ComponentTypeID)
                    return true;
            }
            return false;
        }

      

        //오브젝트가 존재하는지 확인.
        public bool ObjectExists( GObjectID a_GID )
        {
            for (int count = 0; count < m_ComponentInit.GetNumComponentTypeIDS(); count++)
            {
                Dictionary<GObjectID , IComponent> compMap = m_ComponentTypeToComponentMap[count];

                if( m_ComponentTypeToComponentMap[count].ContainsKey(a_GID) == true )
                    return true;                
            }
            return false;
        }

        //Draw
        public void Draw(IIterator drawOrder , GameTime gameTime , SpriteBatch spriteBatch)
        {
            SComponentTypeID typeID;
            for(drawOrder.Begin(); drawOrder.HasNext(); )
            {
                typeID = (SComponentTypeID)drawOrder.GetData();

                Dictionary<GObjectID, IComponent> compMap = m_ComponentTypeToComponentMap[typeID.ComponentTypeID];

                foreach (KeyValuePair<GObjectID, IComponent> pair in compMap)
                {                    
                   ICmpRender render = (ICmpRender)pair.Value;
                   if( render.GetCmpEnable())
                        render.Draw(gameTime, spriteBatch);
                }
            }
        }

        //Update
        public void Update(IIterator updateOrder, GameTime gameTime)
        {
            m_MessageHandleCount++;

            SComponentTypeID typeID;
            for (updateOrder.Begin(); updateOrder.HasNext();)
            {
                typeID = (SComponentTypeID)updateOrder.GetData();
                Dictionary<GObjectID, IComponent> compMap = m_ComponentTypeToComponentMap[typeID.ComponentTypeID];

                foreach (KeyValuePair<GObjectID, IComponent> pair in compMap)
                {
                    ICmpEntity entity = (ICmpEntity)pair.Value;
                    if (entity.GetCmpEnable())
                        entity.Update(gameTime);
                }
            }

            m_MessageHandleCount--;
            RegisterMessageProcess();
        }

        //특정 객체에게만 메세지 전송(컴포넌트 직접전송은 Query이용하여 컴포넌트가 직접 핸들링 호출 가능)
        public void PostMessage(GObjectID a_GID, GMessage a_Msg)
        {
            List<SComponentTypeID> ableCompTypeList = m_ObjectManData.GetMessageTypeInfoList()[a_Msg.m_Type.MessageTypeID];

            m_MessageHandleCount++;

            foreach (SComponentTypeID compTypeID in ableCompTypeList)
            {
                Dictionary<GObjectID, IComponent> compMap = m_ComponentTypeToComponentMap[compTypeID.ComponentTypeID];
                if (compMap.ContainsKey(a_GID) == false) continue;
                SendMessage(compMap[a_GID], a_Msg);
            }

            m_MessageHandleCount--;
            RegisterMessageProcess();
        }

        //메세지를 처리할 수 있는 특정 컴포넌트에게 전송
        public void BroadCastMessage( GMessage a_Msg )
        {
            List<SComponentTypeID> ableCompTypeList = m_ObjectManData.GetMessageTypeInfoList()[a_Msg.m_Type.MessageTypeID];

            m_MessageHandleCount++;

            foreach (SComponentTypeID compTypeID in ableCompTypeList)
            {
                Dictionary<GObjectID, IComponent> compMap = m_ComponentTypeToComponentMap[compTypeID.ComponentTypeID];


                foreach (KeyValuePair<GObjectID, IComponent> pair in compMap)
                {
                    SendMessage(pair.Value, a_Msg);
                }
            }

            m_MessageHandleCount--;
            RegisterMessageProcess();
        }

        //CID를 이용해 특정 CID에  브로드캐스트한다.(메시지 처리 여부를 보고.) -> 업데이트나 , 드로우 같은 경우에.. 대부분의 CID에서 구현하므로  이용하면 될듯.
        //클래스가 많아지거나, 클래스를 줄이면 렌더를 호출 시 처리된 레이어가 또 호출 되는 문제를 해결하기 위해..
        public void BroadCastMessage(GMessage a_Msg , SComponentTypeID typeID)
        {


            Dictionary<GObjectID, IComponent> compMap = m_ComponentTypeToComponentMap[typeID.ComponentTypeID];
            List<SComponentTypeID> ableCompTypeList = m_ObjectManData.GetMessageTypeInfoList()[a_Msg.m_Type.MessageTypeID];

            //메시지 처리하지 않는 것이다..Exception?
            if (!ableCompTypeList.Contains(typeID))
            {
                throw new ObjectManException("처리하지 않는 메시지를 브로드 캐스트를 사용하려고 합니다.");
            }

            m_MessageHandleCount++;

            foreach (KeyValuePair<GObjectID, IComponent> pair in compMap)
            {
                SendMessage(pair.Value, a_Msg);
            }

            m_MessageHandleCount--;
            RegisterMessageProcess();
        }

        private void RegisterMessageProcess()
        {
            //내부적으로 메시지 처리루프가 끝났을때 삭제 예정되어있는 오브젝트들을 없애도록 한다.
            if (m_MessageHandleCount == 0)
            {
                DeleteObjectList();
                AddComponentList();
            }
        }

        private EMessageResult SendMessage(IComponent component , GMessage a_Msg)
        {
            if( component.GetCmpEnable() )
                return component.HandleMessage(a_Msg);
            return EMessageResult.MR_FALSE;
        }

#region 추가/삭제를 위하여 외부로 보여지는 단일 인터페이스.
        public void AddComponent(IComponent component)
        {
            if (m_MessageHandleCount == 0)
                AddComponentMapDirect(component);
            else
                RegisterAddComponent(component);
        }

        public void DestroyObject(GObjectID objectID)
        {
            if (m_MessageHandleCount == 0)
                DestroyObjectDirect(objectID);
            else
                RegisterDeleteObject(objectID);
        }

        public void DestoryAllObject()
        {
            if (m_MessageHandleCount == 0)
                DestroyAllObjectDirect();
            else
                RegisterDeleteAllObject();
        }
#endregion

#region  등록되어있는 Delete오브젝트 Add컴포넌트 관련 처리(private)
        private void DeleteObjectList()
        {
            foreach (GObjectID objectID in m_DestoryGObjectList)
            {
                DestroyObjectDirect(objectID);
            }

            m_DestoryGObjectList.Clear();
        }

        private void AddComponentList()
        {
            foreach (IComponent component in m_AddComponentList)
            {
                AddComponentMapDirect(component);
            }

            m_AddComponentList.Clear();
        }
#endregion
       
        

#region 삭제/ 추가를 위하여 등록(private)
        private void RegisterAddComponent(IComponent component)
        {
            if (m_MessageHandleCount == 0)
            {//내부적으로 메시지 처리루프가 아닐때는 등록하면 안됨.
                throw new ObjectManException("추가할 수 없습니다");
            }

            //포함되지 않으면 등록한다.
            if (!m_AddComponentList.Contains(component))
                m_AddComponentList.Add(component);
        }

        //오브젝트 삭제를 하기 위해 
        private void RegisterDeleteObject(GObjectID a_GID)
        {
            if (m_MessageHandleCount == 0)
            {//내부적으로 메시지 처리루프가 아닐때는 등록하면 안됨.
                throw new ObjectManException("삭제할 수 없습니다");
            }

            if (m_DestoryGObjectList.Contains(a_GID) == false)
                m_DestoryGObjectList.Add(a_GID);
        }

        //오브젝트 전체 등록
        private void RegisterDeleteAllObject()
        {
            if (m_MessageHandleCount == 0)
            {//내부적으로 메시지 처리루프가 아닐때는 등록하면 안됨.
                throw new ObjectManException("삭제할 수 없습니다");
            }

            for (int count = 0; count < m_ComponentInit.GetNumComponentTypeIDS(); count++)
            {
                Dictionary<GObjectID, IComponent> compMap = m_ComponentTypeToComponentMap[count];
                foreach (KeyValuePair<GObjectID, IComponent> pair in compMap)
                {
                    if (m_DestoryGObjectList.Contains(pair.Key) == false)
                        m_DestoryGObjectList.Add(pair.Key);
                }
            }
        }
#endregion



#region 삭제/추가를 바로 접근해 수행(private)
        //객체의 아이디를 키로 하여 컴포넌트 인스턴스를 자료구조에 등록
        private void AddComponentMapDirect(IComponent a_Component)
        {
            if (m_MessageHandleCount != 0)
            {//내부적으로 메시지 처리루프가 끝났을때 삭제 예정되어있는 오브젝트들을 없애도록 한다. 그외에는 실수라고 보고 Exception을 발생시킨다.
                throw new ObjectManException("추가할 수 없습니다");
            }
            m_ComponentTypeToComponentMap[a_Component.GetComponentTypeID().ComponentTypeID].Add(a_Component.GetObjectID(), a_Component);
            a_Component.Init();
        }



        //전체 컴포넌트 인스턴스 삭제
        private void DestroyAllObjectDirect()
        {
            if (m_MessageHandleCount != 0)
            {//내부적으로 메시지 처리루프가 끝났을때 삭제 예정되어있는 오브젝트들을 없애도록 한다. 그외에는 실수라고 보고 Exception을 발생시킨다.
                throw new ObjectManException("삭제할 수 없습니다");
            }

            for (int count = 0; count < m_ComponentInit.GetNumComponentTypeIDS(); count++)
            {
                Dictionary<GObjectID, IComponent> compMap = m_ComponentTypeToComponentMap[count];
                foreach (KeyValuePair<GObjectID, IComponent> pair in compMap)
                {
                    pair.Value.Release();
                }
                compMap.Clear();
            }
        }


        //해당 오브젝트의 컴포넌트들을 전부 제거
        private void DestroyObjectDirect(GObjectID a_GID)
        {
            if (m_MessageHandleCount != 0)
            { //내부적으로 메시지 처리루프가 끝났을때 삭제 예정되어있는 오브젝트들을 없애도록 한다. 그외에는 실수라고 보고 Exception을 발생시킨다.
                throw new ObjectManException("삭제할 수 없습니다");
            }

            for (int count = 0; count < m_ComponentInit.GetNumComponentTypeIDS(); count++)
            {
                if (m_ComponentTypeToComponentMap[count].ContainsKey(a_GID) == false)
                    continue;

                IComponent component = m_ComponentTypeToComponentMap[count][a_GID];
                component.Release();

                m_ComponentTypeToComponentMap[count].Remove(a_GID);
            }
        }
#endregion

      

     

        public void Init( ObjectManagerData ObjManData )
        {
            m_ObjectManData = ObjManData;
            
            m_ObjectManData.Connect();


            m_ComponentInit = m_ObjectManData.GetCmpInit();

            //구현 컴포넌트별로 Object Key와 IComponent(실제구현 컴포넌트 인스턴스)쌍으로 저장할 자료구조 
            m_ComponentTypeToComponentMap = new Dictionary<GObjectID, IComponent>[m_ComponentInit.GetNumComponentTypeIDS()];

            for (int i = 0; i < m_ComponentInit.GetNumComponentTypeIDS(); i++)
            {
                m_ComponentTypeToComponentMap[i] = new Dictionary<GObjectID, IComponent>();
            }

            //ID생성기 초기화.
            GObjectID.InitGenerator();
        }


        public void Release()
        {
            m_ObjectManData.DisConnect();

            m_DestoryGObjectList.Clear();
            m_DestoryGObjectList = null;
            m_AddComponentList.Clear();
            m_AddComponentList = null;

            //맵 자료구조 삭제
            DestroyAllObjectDirect();

            m_ComponentTypeToComponentMap = null;      
        }
        
        //해당 패밀리의 인스턴스 갯수리턴
        public int GetNumofComponent(SComponentTypeID a_SComponentType)
        {
            return m_ComponentTypeToComponentMap[a_SComponentType.ComponentTypeID].Count;
        }
    }
}
